﻿using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;
using VideoReflector.Models;
using VideoReflector.Views;
using FolderPickerLib;
using BuzzIO;

namespace VideoReflector.Controllers
{
    /// <summary>
    /// Top-level controller for application
    /// Deals with program flow between different screen views and manages passing of model objects.
    /// Defines both application level commands and those for a main menu / home view
    /// </summary>
    public sealed class ApplicationController : NotifyingObject
    {
        #region Private fields

        private Application _app;
        private Session _session;
        private ApplicationSettings _settings;
        private UserControl _currentView;
        private Stack<UserControl> _previousViews;
        private BuzzHandsetDevice _buzzDevice;

        #endregion

        #region Constructor

        /// <summary>
        /// Constructor for ApplicationController
        /// </summary>
        /// <param name="app"> Application object controller is associated with </param>
        public ApplicationController(Application app)
        {
            _app = app;
            _app.ShutdownMode = ShutdownMode.OnMainWindowClose;
            // set up previous views navigation stack
            _previousViews = new Stack<UserControl>();
            // try to load existing settings file - if not present create new default settings
            Exception ex;
            ApplicationSettings.LoadFromFile("Settings.xml", Encoding.UTF8, out _settings, out ex);
            if (ex != null)
            {
                // TODO: load default settings from file
                _settings = GetDefaultSettings();
            }
            // attempt to find buzz handsets if already connected
            TryFindBuzzDevice();
            // set up main window
            _app.MainWindow = CreateMainWindow();
            // create main menu view
            var mainMenuView = new MainMenuView();
            // hook-up to controller
            mainMenuView.DataContext = this;
            CurrentView = mainMenuView;
            // show main window
            _app.MainWindow.Show();
        }

        #endregion // Constructor

        #region Event handlers

        /// <summary>
        /// Display warning message on trying to close window
        /// </summary>
        private void mainWindow_Closing(object sender, CancelEventArgs e)
        {
            if (MessageBox.Show("Are you sure you want to quit?", "Quit Application",
                MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.Yes)
            {
                e.Cancel = false;
                // if buzz device is connected ensure all lights are turned off and dispose of device object
                if (BuzzDeviceConnected)
                {
                    _buzzDevice.SetLights(false, false, false, false);
                    try
                    {
                        _buzzDevice.Dispose();
                    }
                    catch (Exception)
                    {
                        // HACK: CloseHandle call in BuzzHandsetDevice.Dispose() throws an exception for some
                        // reason - catch and ignore as hopefully handles will be released on process exit anyway
                        // (this may be total fantasy?)
                    }
                }

            }
            else
            {
                e.Cancel = true;
            }
        }

        #endregion // Event handlers

        #region Events

        /// <summary>
        /// Event invoked on buzz device being found successfully
        /// </summary>
        public event EventHandler FoundBuzzDevice;

        #endregion // Events

        #region Public properties

        /// <summary>
        /// Current view being displayed in application window
        /// </summary>
        public UserControl CurrentView
        {
            get
            {
                return _currentView;
            }
            private set
            {
                if (_currentView != value)
                {
                    if(_currentView != null)
                        _previousViews.Push(_currentView);
                    _currentView = value;
                    NotifyPropertyChanged("CurrentView");
                }
            }
        }

        /// <summary>
        /// Flag for whether a buzz device controller is currently connected or not
        /// </summary>
        public bool BuzzDeviceConnected
        {
            get
            {
                return _buzzDevice != null;
            }
        }

        #endregion // Public properties

        #region Helpers

        /// <summary>
        /// Helper function to get default settings from application resource for when settings file not found
        /// </summary>
        private ApplicationSettings GetDefaultSettings()
        {
            ApplicationSettings settings;
            var uri = new Uri("pack://application:,,,/Resources/DefaultSettings.xml");
            var resourceInfo = Application.GetResourceStream(uri);
            using (var reader = new System.IO.StreamReader(resourceInfo.Stream))
            {
                string xml = reader.ReadToEnd();
                settings = ApplicationSettings.Deserialize(xml);
            }
            return settings;
        }

        /// <summary>
        /// Helper to set up application main window
        /// </summary>
        /// <returns> MainWindow instance with necessary event hook-ups and data context </returns>
        private Window CreateMainWindow()
        {
            // create main window view
            var mainWindow = new MainWindow();
            // add event handlers
            // warning message on trying to close window
            mainWindow.Closing += mainWindow_Closing;
            // look for Buzz controllers on device arrive
            mainWindow.DeviceArrived += (s, e) => TryFindBuzzDevice();
            // hook-up to controller
            mainWindow.DataContext = this;
            return mainWindow;
        }

        #endregion // Helpers

        #region Main menu commands
        
        #region StartNewSession

        private RelayCommand _startNewSessionCommand;

        /// <summary>
        /// Start capture of new session
        /// </summary>
        public ICommand StartNewSessionCommand
        {
            get
            {
                if (_startNewSessionCommand == null)
                {
                    _startNewSessionCommand = new RelayCommand
                    (
                        param => this.StartNewSession(),
                        param => this.CanStartNewSession()
                    );
                }
                return _startNewSessionCommand;
            }
        }

        private void StartNewSession()
        {
            // create a new session
            _session = new Session(_settings.DefaultClipTagGroups);
            // create a new capture session view
            var captureSessionView = new CaptureSessionView();
            // create new tag controller
            var tagController = new TagController(_session.ClipTags, _settings.ClipLength, _app.Dispatcher);
            // if buzz device connected, hook up button press with tag controller, else have hook-up occur
            // later if subsequently connected
            if (BuzzDeviceConnected)
                tagController.HookUpBuzzDevice(_buzzDevice);
            else
                FoundBuzzDevice += (s, e) => tagController.HookUpBuzzDevice(_buzzDevice);
            // create new session capture controller
            var captureSessionController =
                new CaptureSessionController(_session, _settings, captureSessionView.DisplayContainer, tagController);
            // bind controller and view
            captureSessionView.DataContext = captureSessionController;
            // add handler to controller events
            captureSessionController.Finished += (s, e) =>
            {
                if (e.wasCancelled)
                    GoBack();
                else
                    ReviewSession();
            };
            // show session capture view
            CurrentView = captureSessionView;
        }

        private bool CanStartNewSession()
        {
            return true;
        }

        #endregion // StartNewSession

        #region OpenPreviousSession

        private RelayCommand _openPreviousSessionCommand;

        /// <summary>
        /// Load a previously saved session from file and begin reviewing it
        /// </summary>
        public ICommand OpenPreviousSessionCommand
        {
            get
            {
                if (_openPreviousSessionCommand == null)
                {
                    _openPreviousSessionCommand = new RelayCommand
                    (
                        param => this.OpenPreviousSession(),
                        param => this.CanOpenPreviousSession()
                    );
                }
                return _openPreviousSessionCommand;
            }
        }

        private void OpenPreviousSession()
        {
            // create a new session load controller
            var sessionLoadController = new SessionLoadController(_settings);
            // create new session load view and hook-up to controller
            var sessionLoadView = new SessionLoadView();
            sessionLoadView.DataContext = sessionLoadController;
            // add handlers to controller events
            sessionLoadController.Finished += (s, e) =>
            {
                if (e.wasCancelled)
                    GoBack();
                else
                {
                    _session = e.ChosenSession;
                    ReviewSession();
                }
            };
            // show session load view
            CurrentView = sessionLoadView;
        }

        private bool CanOpenPreviousSession()
        {
            return true;
        }

        #endregion // OpenPreviousSession  
        
        #region ReviewSession helper

        /// <summary>
        /// Helper to allow review of current session either after capture of new session or loading session from file
        /// </summary>
        private void ReviewSession()
        {
            if (_session != null)
            {
                // create new view and contorller objects
                var reviewSessionView = new ReviewSessionView();
                var controller = new ReviewSessionController(_session, _settings, 
                                                    reviewSessionView.ClipPlayerView.DisplayContainer);
                // hook-up view to controller
                reviewSessionView.DataContext = controller;
                // show review session view
                CurrentView = reviewSessionView;
            }
        }

        #endregion // ReviewSession helper

        #endregion // Main menu commands

        #region Application commands

        #region Quit

        private RelayCommand _quitCommand;

        /// <summary>
        /// Quit the application
        /// </summary>
        public ICommand QuitCommand
        {
            get
            {
                if (_quitCommand == null)
                {
                    _quitCommand = new RelayCommand
                    (
                        param => this.Quit(),
                        param => this.CanQuit()
                    );
                }
                return _quitCommand;
            }
        }

        private void Quit()
        {
            _app.MainWindow.Close();
        }

        private bool CanQuit()
        {
            return true;
        }

        #endregion // Quit  
        
        #region EditSettings

        private RelayCommand _editSettingsCommand;

        /// <summary>
        /// Edit the current application settings
        /// </summary>
        public ICommand EditSettingsCommand
        {
            get
            {
                if (_editSettingsCommand == null)
                {
                    _editSettingsCommand = new RelayCommand
                    (
                        param => this.EditSettings(),
                        param => this.CanEditSettings()
                    );
                }
                return _editSettingsCommand;
            }
        }

        private void EditSettings()
        {
            // create a new settings controller
            var settingsController = new SettingsController(_settings);
            // create new settings view and hook-up to controller
            var settingsView = new SettingsView();
            settingsView.DataContext = settingsController;
            // add handlers to controller events
            settingsController.Finished += (s, e) => GoBack();
            // show settings view
            CurrentView = settingsView;
        }

        private bool CanEditSettings()
        {
            return _settings != null && !(CurrentView is SettingsView);
        }

        #endregion // EditSettings  
        
        #region GoBack

        private RelayCommand _goBackCommand;

        /// <summary>
        /// Go back to the previous view (saved on the stack)
        /// </summary>
        public ICommand GoBackCommand
        {
            get
            {
                if (_goBackCommand == null)
                {
                    _goBackCommand = new RelayCommand
                    (
                        param => this.GoBack(),
                        param => this.CanGoBack()
                    );
                }
                return _goBackCommand;
            }
        }

        private void GoBack()
        {
            _currentView = _previousViews.Pop();
            NotifyPropertyChanged("CurrentView");
        }

        private bool CanGoBack()
        {
            return _previousViews != null && _previousViews.Count > 0;
        }

        #endregion // GoBack          

        #region GoToMainMenu

        private RelayCommand _goToMainMenuCommand;

        /// <summary>
        /// Display the main menu view
        /// </summary>
        public ICommand GoToMainMenuCommand
        {
            get
            {
                if (_goToMainMenuCommand == null)
                {
                    _goToMainMenuCommand = new RelayCommand
                    (
                        param => this.GoToMainMenu(),
                        param => this.CanGoToMainMenu()
                    );
                }
                return _goToMainMenuCommand;
            }
        }

        private void GoToMainMenu()
        {
            // create main menu view
            var mainMenuView = new MainMenuView();
            // hook-up to controller
            mainMenuView.DataContext = this;
            CurrentView = mainMenuView;
        }

        private bool CanGoToMainMenu()
        {
            return !(CurrentView is MainMenuView);
        }

        #endregion // GoToMainMenu  
        
        #region SaveSession

        private RelayCommand _saveSessionCommand;

        /// <summary>
        /// Save the current open session
        /// </summary>
        public ICommand SaveSessionCommand
        {
            get
            {
                if (_saveSessionCommand == null)
                {
                    _saveSessionCommand = new RelayCommand
                    (
                        param => this.SaveSession(),
                        param => this.CanSaveSession()
                    );
                }
                return _saveSessionCommand;
            }
        }

        private void SaveSession()
        {
            _session.Save();
        }

        private bool CanSaveSession()
        {
            return _session != null;
        }

        #endregion // SaveSession  

        #region TryFindBuzzDevice

        private RelayCommand _tryFindBuzzDeviceCommand;

        /// <summary>
        /// Attempt to find any buzz handset devices connected to computer, and set event handler
        /// to remove reference on devices being removed
        /// </summary>
        public ICommand TryFindBuzzDeviceCommand
        {
            get
            {
                if (_tryFindBuzzDeviceCommand == null)
                {
                    _tryFindBuzzDeviceCommand = new RelayCommand
                    (
                        param => this.TryFindBuzzDevice(),
                        param => this.CanTryFindBuzzDevice()
                    );
                }
                return _tryFindBuzzDeviceCommand;
            }
        }
        
        private void TryFindBuzzDevice()
        {
            if (_buzzDevice == null)
            {
                _buzzDevice = BuzzHandsetDevice.FindBuzzHandset();
                if (_buzzDevice != null)
                {
                    NotifyPropertyChanged("BuzzDeviceConnected");
                    // ensure all lights start in off state
                    _buzzDevice.SetLights(false, false, false, false);
                    // add handler to remove reference if device is removed
                    _buzzDevice.OnDeviceRemoved += (s, e) =>
                    {
                        _app.Dispatcher.BeginInvoke(new Action( () =>
                                {
                                    _buzzDevice = null;
                                    NotifyPropertyChanged("BuzzDeviceConnected");
                                } 
                            ), DispatcherPriority.Normal);
                    };
                    if (FoundBuzzDevice != null)
                        FoundBuzzDevice(this, new EventArgs());
                }
            }
        }

        private bool CanTryFindBuzzDevice()
        {
            return CurrentView is MainMenuView;
        }

        #endregion // TryFindBuzzDevice  
                                     
        #endregion // Application commands

    }
}
